[Rust] Cloudflare WorkersでR2にアクセスしてみる [TypeScript]
Introduction
Cloudflare R2はAmazon S3互換のオブジェクトストレージです。
エグレス料金がかからず、非常にリーズナブルです。
今回はCloudflare WorkersからR2にアクセスしてみます。
Environment
今回試した環境は以下のとおりです。
- MacBook Pro (13-inch, M1, 2020)
- OS : MacOS 12.4
- Node : v18.2.0
- wrangler : 2.8.1
Cloudflareのアカウントと
wranglerのインストールは設定済みとします。
Setup
まずはR2のセットアップです。
CloudflareのCLIツールであるwranglerを使います。
もしなければnpm or yarnでインストール。
% npm install -g wrangler % wrangler --version ⛅️ wrangler 2.8.1 -------------------
このあとCLIからいろいろ操作するので、
ログインします。
% wrangler login
wranglerでR2にバケットを作成します。
% wrangler r2 bucket create <YOUR BUCKET NAME> ⛅️ wrangler 2.8.1 ------------------- Creating bucket <YOUR BUCKET NAME>.
ちなみに、バケット名に「_」使おうとしたらエラーになったので注意。
これでCloudflareのダッシュボードでもバケットが確認できるし、
↓のコマンドで一覧を取得することもできます。
% wrangler r2 bucket list [ ・・・ { "name": <YOUR BUCKET NAME>, "creation_date": "2023-01-24T04:12:06.019Z" }, ・・・ ]
Create Workers with TypeScript
ではTypeScriptでCloudflare WorkersからR2にアクセスしてみます。
wrangler initでWokersの雛形を作成します。
% mkdir ts-r2 && cd ts-r2 % wrangler init --yes
wrangler.tomlにR2のバケット定義を記述します。
[[r2_buckets]] binding = 'MY_BUCKET' bucket_name = '<YOUR BUCKET NAME>' preview_bucket_name = '<YOUR BUCKET NAME>' #開発用
index.tsを、ここを参考に実装します。
//index.ts interface Env { MY_BUCKET: R2Bucket } export default { async fetch(request:Request, env:Env) { const url = new URL(request.url); const key = url.pathname.slice(1); switch (request.method) { case 'PUT': await env.MY_BUCKET.put(key, request.body); return new Response(`Put ${key} successfully!`); case 'GET': const object = await env.MY_BUCKET.get(key); if (object === null) { return new Response('Object Not Found', { status: 404 }); } const headers = new Headers(); object.writeHttpMetadata(headers); headers.set('etag', object.httpEtag); return new Response(object.body, { headers, }); default: return new Response('Method Not Allowed', { status: 405, headers: { Allow: 'PUT, GET, DELETE', }, }); } }, };
npm startで起動します。
% npm start > ts-r2@0.0.0 start > wrangler dev ⛅️ wrangler 2.8.1 ------------------- Your worker has access to the following bindings: - R2 Buckets: - MY_BUCKET: <YOUR BUCKET NAME> ⬣ Listening at http://0.0.0.0:8787 - http://127.0.0.1:8787 - http://192.168.11.4:8787 - http://192.168.105.1:8787 Total Upload: 1.02 KiB / gzip: 0.46 KiB ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ [b] open a browser, [d] open Devtools, [l] turn on local mode, clear console, [x] to exit │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯
curlで適当なpngファイルを指定してアップロードします。
% curl -XPUT --data-binary "@/path/your/image/something.png" http://127.0.0.1:8787/mykey
この時点でダッシュボードをみると、バケットにファイルが登録されてます。
アップロードが成功したら、ブラウザで
http://127.0.0.1:8787/mykey
にアクセスしてみます。
ブラウザでR2からファイルを取得して表示できれば成功です。
Create Workers with Rust
では次に、Rustを使ってWorkersを実装してみましょう。
npm initでRust用Workersの雛形を生成します。
% npm init cloudflare <Project Name> worker-rust % cd <Project Name>
さきほどと同じく、wrangler.tomlにR2のバケット定義を記述し、
ほかの部分も下記のように少し書き換えます。
[vars] WORKERS_RS_VERSION = "*" [build] command = "cargo install -q worker-build && worker-build --release"
現在では、workersのcrateはGithubから直接もってこないと
R2が使えません。
なので、↓のようにGithubにあるworkers-rsのmainブランチを指定します。
・・・ [dependencies] worker = { git = "https://github.com/cloudflare/workers-rs.git", branch = "main"} ・・・
lib.rsを下記のように修正。
Rust版ではR2からオブジェクトを取得する処理だけ実装します。
※修正した部分だけ抜粋
//lib.rs mod r2; pub struct SharedData { } #[event(fetch)] pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> { ・・・ let data = SharedData {}; let router = Router::with_data(data); router .get_async("/r2/get/:key", r2::get) .run(req, env) .await }
そしてr2.rsの実装です。
/r2/get/:keyにGETでアクセスすると、パスにはいっているキー名で
R2からオブジェクト(ここではpng決め打ち)を取得してレスポンスとして返します。
use worker::*; use crate::SharedData; pub async fn get(_req: Request, ctx: RouteContext<SharedData>) -> Result<Response> { //bindしたバケット名を指定 let bucket = ctx.bucket("MY_BUCKET")?; //パスからキー名を取得 let key = ctx.param("key").unwrap(); let item = bucket.get(key).execute().await?.unwrap(); let item_body = item.body().unwrap(); let bytes = item_body.bytes().await.unwrap(); let response = Response::from_bytes(bytes)?; let mut headers = Headers::new(); headers.set("content-type","image/png")?; Ok(response.with_headers(headers)) }
wrangler devコマンドで起動して、動作確認してみます。
% wrangler dev ⛅️ wrangler 2.8.1 ------------------- Running custom build: cargo install -q worker-build && worker-build --release [INFO]: ? Checking for the Wasm target... [INFO]: ? Compiling to Wasm... [INFO]: ⬇️ Installing wasm-bindgen... [INFO]: Optimizing wasm binaries with `wasm-opt`... [INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended [INFO]: ✨ Done in 1.15s ⚡ Done in 10ms ・・・ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ [b] open a browser, [d] open Devtools, [l] turn on local mode, clear console, [x] to exit │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯
ブラウザで下記URLにアクセスしてみます。
http://127.0.0.1:8787/r2/get/<R2のファイル名>
R2に登録した画像が表示されれば、Rust版でも動作確認OKです。